﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;

namespace NetPackage
{
    public class NetClient : IDisposable
    {

        public NetClient(string host, int port, Package package, INetClientHandler handler)
        {
            System.Net.IPAddress[] ips = System.Net.Dns.GetHostAddresses(host);
            mHost = ips[0];
            mPort = port;
            Handler = handler;
            mPackage = package;
            mPackage.Receive = OnDataReceive;
            mReceiveSAEA = new SocketAsyncEventArgs();
            mReceiveSAEA.Completed += ReceiveCompleted;
            mReceiveSAEA.SetBuffer(new byte[1024 * 4], 0, 1024 * 4); ;
        }

        private System.Net.Sockets.SocketAsyncEventArgs mReceiveSAEA;

        private Package mPackage;

        public INetClientHandler Handler
        {
            get;
            set;
        }

        private System.Net.IPAddress mHost;

        private int mPort;

        private System.Net.Sockets.Socket mSocket;

        private bool mConnected = false;

        private bool mLittleEndian = true;

        public bool Connected
        {
            get
            {
                return mConnected;
            }
        }

        public Exception LastError
        {
            get;
            private set;
        }

        public bool Connect()
        {
            if (Connected)
                return true;
            try
            {
                mPackage.Reset();
                mSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                mSocket.Connect(mHost, mPort);
                BeginReceive();
                Handler.Connected(this);
                return true;
            }
            catch (Exception e_)
            {
                OnError(e_);
                return false;
            }

        }

        private void OnError(Exception e)
        {
            try
            {
                Handler.ClientError(this, e);
            }
            catch
            {

            }
        }

        private void BeginReceive()
        {
            try
            {
                if (!mSocket.ReceiveAsync(mReceiveSAEA))
                {
                    ReceiveCompleted(this,mReceiveSAEA);
                }
            }
            catch (SocketException se)
            {
                ReleaseSocket();
                OnError(se);
            }
            catch (ObjectDisposedException ode)
            {
                ReleaseSocket();
                OnError(ode);
            }
            catch (Exception e_)
            {
                OnError(e_);

            }
        }

        private void ReceiveCompleted(object sender, System.Net.Sockets.SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
            {
                try
                {
                    mPackage.Import(e.Buffer, 0, e.BytesTransferred);
                    BeginReceive();
                }
                catch (Exception e_)
                {
                    ReleaseSocket();
                    OnError(e_);
                }
               
            }
            else
            {
                ReleaseSocket();
            }

        }

        private void OnDataReceive(System.IO.Stream stream)
        {
            try
            {
                stream.Position = 0;
                BufferStream reader = new BufferStream(stream, LittleEndian);
                IMessage msg = mPackage.FromStream(reader);
                if (msg == null)
                    throw new Exception("message type not found!");
                Handler.ClientReceive(this, mPackage.ReceiveCast(msg));
            }
            catch (Exception e_)
            {
                OnError(e_);
            }
            finally
            {
                mPackage.Reset();
            }
        }

        public bool LittleEndian
        {
            get
            {
                return mLittleEndian;
            }
            set
            {
                mLittleEndian = value;
                mPackage.LittleEndian = LittleEndian;
            }
        }

        public bool Send(object message)
        {
            lock (this)
            {
                if (Connect())
                {

                    try
                    {
                        IMessage msg = mPackage.SendCast(message);
                        byte[] data = mPackage.GetMessageData(msg);
                        int count = data.Length;
                        int index = 0;
                        int scount;
                        while (count > 0)
                        {
                            scount = mSocket.Send(data, index, count, SocketFlags.None);
                            count -= scount;
                            index += scount;
                        }

                        return true;
                    }
                    catch (SocketException se)
                    {
                        ReleaseSocket();
                        OnError(se);
                    }
                    catch (ObjectDisposedException ode)
                    {
                        ReleaseSocket();
                        OnError(ode);
                    }
                    catch (Exception e_)
                    {
                        OnError(e_);

                    }
                }
                return false;
            }

        }

        private void ReleaseSocket()
        {
            mConnected = false;
            lock (this)
            {
                if (mSocket != null)
                {
                    Handler.ClientDisposed(this);
                    try
                    {
                        if (mSocket != null)
                        {
                            mSocket.Shutdown(SocketShutdown.Both);
                        }
                    }
                    catch
                    {
                    }
                    try
                    {
                        if (mSocket != null)
                        {
                            mSocket.Close();
                        }
                    }
                    catch
                    {
                    }
                }
                mSocket = null;

            }

        }

        public void Dispose()
        {
            ReleaseSocket();
        }
    }
}
